/* Copyright (c) [2020] juruoyun developer team
   Juruoyun basic lib is licensed under the Mulan PSL v1.
   You can use this software according to the terms and conditions of the Mulan PSL v1.
   You may obtain a copy of Mulan PSL v1 at:
      http://license.coscl.org.cn/MulanPSL
   THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
   PURPOSE.
   See the Mulan PSL v1 for more details.*/
#include "jbl_file.h"
#if JBL_FILE_ENABLE==1
#include <sys/stat.h>
#include "jbl_log.h"
#include "jbl_string.h"
#include "jbl_string_cc.h"
#include "jbl_file_ct.h"
#include <errno.h>
#include <stdio.h>
jbl_var_operators_new(jbl_file_operators,jbl_file_free,jbl_file_copy,NULL,NULL,jbl_file_view_put,NULL);
jbl_file * jbl_file_new()
{
	jbl_file * this=jbl_malloc(sizeof(jbl_file));
	jbl_gc_init(this);
	jbl_gc_plus(this);//增加引用计数
	jbl_pthread_lock_init(this);
	jbl_var_set_operators(this,&jbl_file_operators);
	this->dir					=NULL;
	this->handle				=NULL;
	this->type					=JBL_FILE_CLOSE;
	this->status.size			=0;
	this->ctid					=JBL_FILE_CT_UNKNOW;
#if JBL_TIME_ENABLE==1
	this->status.time_access	=NULL;
	this->status.time_modify	=NULL;
	this->status.time_creat		=NULL;
#endif
#if JBL_FILE_CACHE_GB2312NAME==1 && defined(_WIN32)
	this->dir_gb2312			=NULL;
#endif
	return this;
}
jbl_file* jbl_file_free(jbl_file *this)
{
	if(!this)return NULL;
    jbl_pthread_lock_wrlock(this);
	jbl_gc_minus(this);
	if(!jbl_gc_refcnt(this))
	{
		if(jbl_gc_is_ref(this))
			jbl_file_free((jbl_file*)(((jbl_reference*)this)->ptr));
		else
			this=jbl_file_close(this);
        jbl_pthread_lock_free(this);
		jbl_free(this);
	}
    else{jbl_pthread_lock_unwrlock(this);}
	return NULL;
}
JBL_INLINE jbl_file *jbl_file_copy(jbl_file *that)
{
	if(!that)return NULL;
    jbl_pthread_lock_wrlock(that);
	jbl_gc_plus(that);
    jbl_pthread_lock_unwrlock(that);    
	return that;
}
jbl_uint8 jbl_file_readable(jbl_file *this)
{
	if(!this)return 0;
	jbl_file *thi=jbl_refer_pull_rdlock(this);
    jbl_uint8 ans=thi->type==JBL_FILE_READ||thi->type==JBL_FILE_RW||thi->type==JBL_FILE_WR;
    jbl_refer_pull_unrdlock(this);
	return ans;
} 
jbl_uint8 jbl_file_writeable(jbl_file *this)
{
	if(!this)return 0;
	jbl_file *thi=jbl_refer_pull_rdlock(this);
    jbl_uint8 ans=thi->type==JBL_FILE_WRITE||thi->type==JBL_FILE_RW||thi->type==JBL_FILE_WR;
    jbl_refer_pull_unrdlock(this);
	return ans;
}
static jbl_uint8 need_change_handle(jbl_file *this,jbl_file_handle_type target)
{
	if(!this)return 1;
	jbl_file *thi=jbl_refer_pull_rdlock(this);
    jbl_uint8 ans=1;
    switch(target)
    {
        case JBL_FILE_CLOSE:
            if(thi->type==JBL_FILE_CLOSE)
                ans=0;
           break;
        case JBL_FILE_READ:
            if(thi->type==JBL_FILE_READ||thi->type==JBL_FILE_RW||thi->type==JBL_FILE_WR)
                ans=0;
           break;
        case JBL_FILE_WRITE:
            if(thi->type==JBL_FILE_WRITE||thi->type==JBL_FILE_RW||thi->type==JBL_FILE_WR)
                ans=0;
           break;
        case JBL_FILE_RW:
            if(thi->type==JBL_FILE_RW||thi->type==JBL_FILE_WR)
                ans=0;
           break;
        case JBL_FILE_WR:
            if(thi->type==JBL_FILE_RW||thi->type==JBL_FILE_WR)
                ans=0;
           break;
        
    }        
    jbl_refer_pull_unrdlock(this);
    return ans;
}
jbl_file* jbl_file_close(jbl_file *this)
{
	if(!this)jbl_exception("NULL POINTER");
	jbl_file *thi=jbl_refer_pull_wrlock(this);	
	thi->dir					=jbl_string_free(thi->dir);
	thi->status.size			=0;
	thi->ctid					=JBL_FILE_CT_UNKNOW;
	if(thi->handle)				fclose(thi->handle);
	thi->handle					=NULL;
	thi->type					=JBL_FILE_CLOSE;
#if JBL_TIME_ENABLE==1
	thi->status.time_access		=jbl_time_free(thi->status.time_access);
	thi->status.time_modify		=jbl_time_free(thi->status.time_modify);
	thi->status.time_creat		=jbl_time_free(thi->status.time_creat);
#endif	
#if JBL_FILE_CACHE_GB2312NAME==1 && defined(_WIN32)
	thi->dir_gb2312				=jbl_string_free(thi->dir_gb2312);
#endif
    jbl_refer_pull_unwrlock(this);
	return this;
}
jbl_file* jbl_file_open(jbl_file *this,jbl_string * dir,jbl_file_handle_type type)
{
	if(!this)this=jbl_file_new();
	jbl_file *thi=jbl_refer_pull_wrlock(this);
	if(thi->dir)thi	=jbl_refer_pull(this=jbl_file_close(this));
	thi->dir		=jbl_string_copy(dir);
	thi->dir		=jbl_string_set_tail(thi->dir);	
	thi->ctid		=jbl_file_get_ctid_by_name(thi->dir);
#if defined(_WIN32) && JBL_FILE_CACHE_GB2312NAME==1
	thi->dir_gb2312			=jbl_string_to_gb2312_from_utf8(NULL,thi->dir);
	thi->dir_gb2312			=jbl_string_set_tail(thi->dir_gb2312);
#endif
	thi=jbl_file_change_handle(thi,type);
    jbl_refer_pull_unwrlock(this);
	return this;
}
JBL_INLINE jbl_file* jbl_file_open_chars(jbl_file *this,unsigned char * dir,jbl_file_handle_type type)
{
    jbl_string * name=jbl_string_add_const(NULL,dir);
    jbl_file* file=jbl_file_open(this,name,type);
    name=jbl_string_free(name);
    return file;
}
jbl_file* jbl_file_change_handle(jbl_file *this,jbl_file_handle_type type)
{
	if(!this)jbl_exception("NULL POINTER");
	jbl_file *thi=jbl_refer_pull_wrlock(this);
	if(need_change_handle(thi,type)&&thi->dir)
	{
		if(thi->handle||this->type!=JBL_FILE_CLOSE)fclose(thi->handle),thi->handle=NULL,this->type=JBL_FILE_CLOSE;
#ifdef _WIN32
#if JBL_FILE_CACHE_GB2312NAME==1
		jbl_string * gb2312name	=jbl_string_copy(thi->dir_gb2312);
#else
		jbl_string * gb2312name	=jbl_string_to_gb2312_from_utf8(NULL,thi->dir);//windows的系统要求gb2312
		gb2312name				=jbl_string_set_tail(gb2312name);
#endif
		#define name gb2312name
#elif defined(__APPLE__) || defined(__linux__)
		#define name thi->dir
#endif	
		switch(thi->type=type)
		{
			case JBL_FILE_CLOSE	:break;
			case JBL_FILE_READ	:thi->handle=fopen((char*)jbl_string_get_chars(name),"rb");break;
			case JBL_FILE_WRITE	:thi->handle=fopen((char*)jbl_string_get_chars(name),"wb");break;
			case JBL_FILE_RW	:thi->handle=fopen((char*)jbl_string_get_chars(name),"rb+");break;
			case JBL_FILE_WR	:thi->handle=fopen((char*)jbl_string_get_chars(name),"wb+");break;
		}
#undef name
#ifdef _WIN32
		gb2312name=jbl_string_free(gb2312name);	
#endif
        if(thi->handle)
            thi=jbl_file_update_status(thi);
        else
        {
            jbl_log(UC"open file %j failed",thi->dir);		
            thi=jbl_file_close(thi);
        }
	}
    jbl_refer_pull_unwrlock(this);
	return this;
}
jbl_file* jbl_file_update_status(jbl_file *this)
{
	if(!this)jbl_exception("NULL POINTER");
	jbl_file *thi=jbl_refer_pull_wrlock(this);
#ifdef _WIN32
	struct __stat64 buf;
#if JBL_FILE_CACHE_GB2312NAME==1
	jbl_string * gb2312name	=jbl_string_copy(thi->dir_gb2312);
#else
	jbl_string * gb2312name	=jbl_string_to_gb2312_from_utf8(NULL,thi->dir);//windows的系统要求gb2312
	gb2312name				=jbl_string_set_tail(gb2312name);
#endif
	if(_stat64((char*)jbl_string_get_chars(gb2312name),&buf))
	{
		thi->dir=jbl_string_free(thi->dir);
		gb2312name=jbl_string_free(gb2312name);	
		jbl_log(UC"open file %j failed with errno %d",thi->dir,errno);
		goto exit;
	}
	gb2312name=jbl_string_free(gb2312name);	
#elif defined(__APPLE__) || defined(__linux__)
	struct stat buf;
	if(stat((char*)jbl_string_get_chars(thi->dir),&buf))
	{
		jbl_log(UC"open file %j failed with errno %d",thi->dir,errno);
		thi->dir=jbl_string_free(thi->dir);
		goto exit;
	}
#endif
	thi->status.size		=(jbl_uint64)buf.st_size;
#if JBL_TIME_ENABLE==1
	thi->status.time_access	=jbl_time_set(thi->status.time_access,buf.st_atime*1000);
	thi->status.time_modify	=jbl_time_set(thi->status.time_modify,buf.st_mtime*1000);
	thi->status.time_creat	=jbl_time_set(thi->status.time_creat ,buf.st_ctime*1000);
#endif
exit:;
    jbl_refer_pull_unwrlock(this);
	return this;
}
jbl_string * jbl_file_read(jbl_file * this,jbl_string*res,jbl_uint64 start,jbl_uint64 end)
{
	if(!this)jbl_exception("NULL POINTER");
	jbl_file *thi=jbl_refer_pull_wrlock(this);
	thi=jbl_file_change_handle(thi,JBL_FILE_READ);
	if(thi->type==JBL_FILE_WRITE||thi->type==JBL_FILE_CLOSE)jbl_exception("Unreadable file");
	jbl_min_update(end,thi->status.size);
	if(start==-1ULL)
		start=0;
	else
	{
		jbl_max_update(end,start);
		jbl_min_update(start,end);
		thi=jbl_file_set_offset(thi,start);
	}
	jbl_string *re;res=jbl_string_extend_to(res,end-start,1,&re);jbl_string_hash_clear(re);
	re->len+=fread(re->s+re->len,1,end-start,thi->handle);
    jbl_refer_pull_unwrlock(res);
    jbl_refer_pull_unwrlock(this);
	return res;
}
jbl_file * jbl_file_write(jbl_file * this,jbl_string*out)
{
	if(!this)jbl_exception("NULL POINTER");
	jbl_file *thi=jbl_refer_pull_wrlock(this);
	thi=jbl_file_change_handle(thi,JBL_FILE_WRITE);
	if(thi->type==JBL_FILE_READ||thi->type==JBL_FILE_CLOSE)jbl_exception("Unwriteable file");
	jbl_string * ou=jbl_refer_pull_rdlock(out);
	fwrite(ou->s,1,ou->len,this->handle);
    jbl_refer_pull_unrdlock(out);
    jbl_refer_pull_unwrlock(this);
	return this;
}
JBL_INLINE jbl_file * jbl_file_set_offset(jbl_file * this,jbl_uint64 start)
{
	if(!this)jbl_exception("NULL POINTER");
	jbl_file *thi=jbl_refer_pull_wrlock(this);	
	jbl_min_update(start,thi->status.size);
#ifdef _WIN64
	_fseeki64(thi->handle,(jbl_int64)start,SEEK_SET);
#else
	fseek(thi->handle,(long int)start,SEEK_SET);
#endif	
    jbl_refer_pull_unwrlock(this);
	return this;
}
jbl_uint64 jbl_file_get_size(jbl_file * this)
{
	if(!this)return 0;
	jbl_file *thi=jbl_refer_pull_rdlock(this);
    jbl_uint64 ans=thi->status.size;
    jbl_refer_pull_unrdlock(this);
	return ans;
}
#if JBL_STREAM_ENABLE==1
/*******************************************************************************************/
/*                            以下函数实现文件的浏览操作                               */
/*******************************************************************************************/
jbl_file* jbl_file_view_put(jbl_file* this,jbl_stream *out,jbl_uint8 format,jbl_uint32 tabs,jbl_uint32 line,unsigned char * varname,unsigned char * func,unsigned char * file)
{
	jbl_file *thi=jbl_refer_pull_rdlock(this);
    if(jbl_stream_view_put_format(thi,out,format,tabs,UC"jbl_file",line,varname,func,file))
    {
        jbl_stream_push_char(out,'\n');
        ++tabs;
        char * type		[]={"close","read","write","RW","WR"};
        for(jbl_uint32 j=0;j<tabs;jbl_stream_push_chars(out,UC JBL_VIEW_TAB),++j){}	jbl_stream_push_chars(out,UC"handle type  :");jbl_stream_push_chars(out,UC type[thi->type])								;jbl_stream_push_char(out,'\n');
        for(jbl_uint32 j=0;j<tabs;jbl_stream_push_chars(out,UC JBL_VIEW_TAB),++j){}	jbl_stream_push_chars(out,UC"dir          :");jbl_stream_push_string(out,thi->dir)										;jbl_stream_push_char(out,'\n');
        for(jbl_uint32 j=0;j<tabs;jbl_stream_push_chars(out,UC JBL_VIEW_TAB),++j){}	jbl_stream_push_chars(out,UC"size         :");jbl_stream_push_uint(out,thi->status.size)								;jbl_stream_push_char(out,'\n');
        for(jbl_uint32 j=0;j<tabs;jbl_stream_push_chars(out,UC JBL_VIEW_TAB),++j){}	jbl_stream_push_chars(out,UC"content type :");jbl_stream_push_chars(out,jbl_file_get_ct_chars_by_ctid(thi->ctid))		;jbl_stream_push_char(out,'\n');
        for(jbl_uint32 j=0;j<tabs;jbl_stream_push_chars(out,UC JBL_VIEW_TAB),++j){}	jbl_stream_push_chars(out,UC"suffix       :");jbl_stream_push_chars(out,jbl_file_get_suffix_chars_by_ctid(thi->ctid))	;jbl_stream_push_char(out,'\n');
#if JBL_TIME_ENABLE==1
        for(jbl_uint32 j=0;j<tabs;jbl_stream_push_chars(out,UC JBL_VIEW_TAB),++j){}	jbl_stream_push_chars(out,UC"time_creat   :");jbl_stream_push_time(out,thi->status.time_creat ,UC"Y-m-d H:i:s.u")		;jbl_stream_push_char(out,'\n');
        for(jbl_uint32 j=0;j<tabs;jbl_stream_push_chars(out,UC JBL_VIEW_TAB),++j){}	jbl_stream_push_chars(out,UC"time_modify  :");jbl_stream_push_time(out,thi->status.time_modify,UC"Y-m-d H:i:s.u")		;jbl_stream_push_char(out,'\n');
        for(jbl_uint32 j=0;j<tabs;jbl_stream_push_chars(out,UC JBL_VIEW_TAB),++j){}	jbl_stream_push_chars(out,UC"time_access  :");jbl_stream_push_time(out,thi->status.time_access,UC"Y-m-d H:i:s.u")		;jbl_stream_push_char(out,'\n');
#endif
    }
    else jbl_stream_push_char(out,'\n');
    jbl_refer_pull_unwrlock(out);
    jbl_refer_pull_unrdlock(this);
	return this;
}
jbl_stream * jbl_file_stream_new(jbl_file *file)
{
	jbl_stream* this=jbl_stream_new(&jbl_file_stream_operators);
    this->data[0].p=jbl_file_copy(file);
	this->data[1].u=0;
	this->data[2].u=-1ULL;
	return this;
}
static void __sff(jbl_stream* thi){jbl_file_free((jbl_file*)thi->data[0].p);}
static void __fso(jbl_stream* thi,jbl_uint8 force)
{
	if(!thi)jbl_exception("NULL POINTER");
	jbl_stream* nxt=jbl_refer_pull_wrlock(thi->nxt);
    jbl_file *file=jbl_refer_pull_wrlock((jbl_file*)thi->data[0].p);
    if(nxt)
	{
        jbl_stream_get_buf(thi,1);
		file=jbl_file_change_handle(file,JBL_FILE_READ);
		if(file->type==JBL_FILE_WRITE||file->type==JBL_FILE_CLOSE)jbl_exception("Unreadable file");
		jbl_min_update(thi->data[1].u,file->status.size);
		jbl_min_update(thi->data[2].u,file->status.size);
		file=jbl_file_set_offset(file,thi->data[1].u);
		while(thi->data[1].u<thi->data[2].u)
		{
            if(nxt->buf->size-nxt->buf->len>JBL_FILE_STREAM_COMMIT_LENGTH)
            {
                jbl_stream_buf_size_type len=(jbl_stream_buf_size_type)jbl_min((thi->data[2].u-thi->data[1].u),(nxt->buf->size-nxt->buf->len));
                len=(jbl_stream_buf_size_type)fread((char*)nxt->buf->s+nxt->buf->len,1,len,file->handle);
                nxt->buf->len+=len;
                thi->data[1].u+=len;
            }
            jbl_stream_push_down(nxt,0);
            jbl_stream_move_unhandle_buf(nxt->buf);
            if(1==(thi->stop=nxt->stop))break;
		}
        nxt->op->op(nxt,force);
	}
    else if(thi->buf)
    {
        if(thi->buf->len-thi->buf->sta>JBL_FILE_STREAM_COMMIT_LENGTH||force)
        {
            file=jbl_file_change_handle(file,JBL_FILE_WRITE);
            if(file->type==JBL_FILE_READ||file->type==JBL_FILE_CLOSE)jbl_exception("Unwriteable file");
            thi->buf->sta+=(jbl_stream_buf_size_type)fwrite(thi->buf->s+thi->buf->sta,1,thi->buf->len-thi->buf->sta,file->handle);
        }
    }
    jbl_refer_pull_unwrlock((jbl_file*)thi->data[0].p);
    jbl_refer_pull_unwrlock(thi->nxt);
}
jbl_stream_operators_new(jbl_file_stream_operators,__fso,__sff,NULL,JBL_FILE_STREAM_BUF_LENGTH,3);
#endif
#endif
